package com.example.demo.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Order(2)
@Component
public class SystemLogAspect {
	
	private static ObjectMapper mapper = new ObjectMapper();
	
	private String toJSONString(Object object) {
		try {
			return mapper.writeValueAsString(object);
		} catch (JsonProcessingException e) {
			return "";
		}
	}
	
	@Pointcut("within(@org.springframework.stereotype.Controller *)")
	public void controllerAnnotationPointcut() {
	}
	
	@Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
	public void restControllerAnnotationPointcut() {
	}
	
	@Before("controllerAnnotationPointcut() || restControllerAnnotationPointcut()")
	public void before() {
	}
	
	@Around("controllerAnnotationPointcut() || restControllerAnnotationPointcut()")
	public Object log(ProceedingJoinPoint pjp) throws Throwable {
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		if (servletRequestAttributes == null) {
			return pjp.proceed();
		}
		//记录log
		HttpServletRequest request = servletRequestAttributes.getRequest();
		String requestUrl = StringUtils.trimToEmpty(request.getRequestURI());
		
		long start = System.nanoTime();
		String queryParams = StringUtils.trimToEmpty(request.getQueryString());
		List<Object> bodyParams = new ArrayList<>();
		String requestMethod = request.getMethod();
		if (StringUtils.equalsIgnoreCase("POST", requestMethod)) {
			List<Object> bodyArgs = Arrays.asList(pjp.getArgs());
			for (Object object : bodyArgs) {
				if (object instanceof HttpServletRequest) {
					Map<String, Object> params = new TreeMap<String, Object>();
					Set<String> paramsKey = request.getParameterMap().keySet();
					for (String key : paramsKey) {
						params.put(key, request.getParameter(key));
					}
					bodyParams.add(params);
					continue;
				} else if (object instanceof HttpServletResponse) {
					continue;
				} else if (object instanceof BindingResult) {
					continue;
				} else if (object instanceof MultipartFile) {
					continue;
				}
				bodyParams.add(object);
			}
		}
		String simpleClassName = pjp.getTarget().getClass().getSimpleName(); // pjp.getSignature().getDeclaringType().getSimpleName()
		String method = pjp.getSignature().getName();
		Object result = pjp.proceed();
		String resultJson = null;
		if (result instanceof ModelAndView && (((ModelAndView) result).getView() instanceof MappingJackson2JsonView)) {
			resultJson = this.toJSONString(((ModelAndView) result).getModelMap());
		} else {
			resultJson = this.toJSONString(result);
		}
		
		List<Object> logParams = new ArrayList<>();
		long millis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
		StringBuilder logText = new StringBuilder("{}请求{}类{}方法,url={},耗{}毫秒,slow[{}s-{}s]");
		logParams.add(requestMethod);
		logParams.add(simpleClassName);
		logParams.add(method);
		logParams.add(requestUrl);
		logParams.add(millis);
		logParams.add((millis / 1000));
		logParams.add(((millis / 1000) + 1));
		if (StringUtils.isNotBlank(queryParams)) {
			logText.append(",入参={}");
			logParams.add(queryParams);
		}
		if (!bodyParams.isEmpty()) {
			logText.append(",body={}");
			logParams.add(this.toJSONString(bodyParams));
		}
		if (StringUtils.isNotBlank(resultJson)) {
			logText.append(",出参={}");
			logParams.add(resultJson);
		}
		logText.trimToSize();
		
		log.info(logText.toString(), logParams.toArray());
		return result;
	}
	
	@After("controllerAnnotationPointcut() || restControllerAnnotationPointcut()")
	public void after() {
	}
	
	
	public static void main(String[] args) {
		
		long t = 500;
		
		/*slow-500ms
		slow-1s
		slow-2s
		slow-3s
		slow-4s
		slow-5s
		slow-6s
		slow-8s
		slow-*/
		
		
		long slow = (t / 1000);
		System.out.println("slow:[" + slow + "s-" + (slow + 1) + "s]");
		
		
	}
}
